package com.hy.ratelimiting.limiting;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created With IntelliJ IDEA.
 * Descriptions:滑动窗口限流算法
 * <p>
 * User:Mr.Du
 * Date:2024/4/25
 * Time:21:20
 */
@Slf4j
@Service
public class SlidingWindowRateLimiter {

    private long unitOfTime = 60;

    private int subCycle = 10;

    private int thresholdPerMin = 100;

    private final ConcurrentHashMap<Long, Integer> counters = new ConcurrentHashMap<>();


    /**
     * 滑动窗口时间算法实现
     */
    public boolean allowRequest() {

        // 将当前时间戳（以 UTC 时区为准）进行舍去操作，使其对齐到最近的子周期的起始时间
        // 例如，如果子周期长度为 10 秒，并且当前时间戳是 1714289427，那么上述表达式的结果将是 1714289420，即对齐到最近的以 0 结尾的子周期起始时间。
        long currentWindowTime = System.currentTimeMillis() / 1000l / subCycle * subCycle; //获取当前时间在哪个小周期窗口
        log.info("当前时间2: {}", currentWindowTime);
        int currentWindowNum = countCurrentWindow(currentWindowTime); //当前窗口总请求数

        //超过阀值限流
        if (currentWindowNum >= thresholdPerMin) {
            return false;
        }
        //计数器+1
//        counters.put(currentWindowTime, counters.getOrDefault(currentWindowTime, 0) + 1);
        counters.merge(currentWindowTime, 1, Integer::sum);
        return true;
    }

    /**
     * 统计当前窗口的请求数
     */
    private int countCurrentWindow(long currentWindowTime) {
        //计算窗口开始位置
        long startTime = currentWindowTime - subCycle * (unitOfTime / subCycle - 1);
        log.info("当前窗口开始时间: {}, 当前窗口结束时间: {}", startTime);
        int count = 0;

        //遍历存储的计数器
        Iterator<Map.Entry<Long, Integer>> iterator = counters.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, Integer> entry = iterator.next();
            // 删除无效过期的子窗口计数器
            if (entry.getKey() < startTime) {
                iterator.remove();
            } else {
                //累加当前窗口的所有计数器之和
                count += entry.getValue();
            }
        }
        return count;
    }
}
